package com.gcalsync.store;

import com.gcalsync.log.*;

import javax.microedition.rms.InvalidRecordIDException;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
import javax.microedition.rms.RecordStoreNotOpenException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

/**
 * @author Thomas Oldervoll, thomas@zenior.no
 * @author $Author$
 * @version $Rev: 22 $
 * @date $Date$
 */
public class StoreController {

    private String name;
    private RecordStore recordStore;
    private RecordTypeMapper recordTypeMapper;
    private RecordTypeFilter recordFilter = new RecordTypeFilter();

    public StoreController(String name, RecordTypeMapper recordTypeMapper) {
        this.name = name;
        this.recordTypeMapper = recordTypeMapper;
    }

    public Storable read(int recordId) {
        openRecordStore();
        Storable object = null;
        object = readRecord(recordId);
        closeRecordStore();
        return object;
    }

    private Storable readRecord(int recordId) {
        ByteArrayInputStream byteIn = null;
        DataInputStream dataIn = null;
        byte recordType = -1;
        Storable object = null;
        try {
            byte[] record = recordStore.getRecord(recordId);
            recordType = record[0];
            byteIn = new ByteArrayInputStream(record, 1, record.length - 1);
            dataIn = new DataInputStream(byteIn);
            StorableFactory factory = recordTypeMapper.getFactory(recordType);
            object = factory.create();
            object.recordId = recordId;
            object.readRecord(dataIn);
            System.out.println("Read record " + recordId + " of type " + recordType);
        } catch (InvalidRecordIDException e) {
            throw new StoreException("Failed to read record " + recordId + " ( " + recordType + " from " + name, e);
        } catch (IOException e) {
            throw new StoreException("Failed to read record " + recordId + " ( " + recordType + " from " + name, e);
        } catch (RecordStoreException e) {
            throw new StoreException("Failed to read record " + recordId + " ( " + recordType + ") from " + name, e);
        } finally {
            if (dataIn != null) {
                try {
                    dataIn.close();
                } catch (IOException e) {
                    ErrorHandler.log.append("Failed to close dataIn due to " + e);
                    e.printStackTrace();
                }
            }
            if (byteIn != null) {
                try {
                    byteIn.close();
                } catch (IOException e) {
                    ErrorHandler.log.append("Failed to close byteIn due to " + e);
                    e.printStackTrace();
                }
            }
        }
        return object;
    }

    public Storable read(byte recordType) {
        Storable[] objects = readAll(recordType);
        if (objects == null || (objects != null && objects.length == 0)) {
            return null;
        }
		
        if (objects.length > 1) {
            throw new StoreException("name is not unique, found " + objects.length + " records");
        }
        return objects[0];
    }

    public Storable[] readAll(byte recordType) {
        openRecordStore();
        RecordEnumeration recordEnumeration = getRecordEnumeration(recordType);
        int recordCount = recordEnumeration.numRecords();
        Storable[] result = new Storable[recordCount];

        try {
            for (int i = 0; i < recordCount; i++) {
                try {
                    result[i] = readRecord(recordEnumeration.nextRecordId());
                } catch (InvalidRecordIDException e) {
                    throw new StoreException("Failed to read record from " + name, e);
                }
            }
        } finally {
            recordEnumeration.destroy();
            closeRecordStore();
        }

        return result;
    }

	public void deleteRecords(byte recordType)
	{
		try
		{
			//get all records of the specified type
			Storable[] st = readAll(recordType);

			//open the record store to begin deleting records,
			//delete the records, and then close the store
			openRecordStore();

			for (int i=0; i<st.length; i++)
				recordStore.deleteRecord(st[i].recordId);

			closeRecordStore();
		}
		catch (Exception e)
		{
			System.out.println("deleteRec(): " + e);
		}
	}

    public void write(Storable object) {
        openRecordStore();
        writeRecord(object);
        closeRecordStore();
    }

    private void writeRecord(Storable object) {
        if (object == null) {
            throw new IllegalArgumentException("Attempt to store null object");
        }
        ByteArrayOutputStream byteOut = null;
        DataOutputStream dataOut = null;
        try {
            byteOut = new ByteArrayOutputStream();
            byteOut.write(object.recordType);
            dataOut = new DataOutputStream(byteOut);
            object.writeRecord(dataOut);
            dataOut.flush();
            byte [] newRecord = byteOut.toByteArray();
            openRecordStore();
            if (object.recordId > 0) {
                recordStore.setRecord(object.recordId, newRecord, 0, newRecord.length);
            } else {
                object.recordId = recordStore.addRecord(newRecord, 0, newRecord.length);
            }
            System.out.println("Wrote record " + object.recordId + " of type " + object.recordType);
        } catch (RecordStoreException e) {
            throw new StoreException("Failed to write record " + object.recordId + " ( " + object + ") to " + name, e);
        } catch (IOException e) {
            throw new StoreException("Failed to write record " + object.recordId + " ( " + object + ") to " + name, e);
        } finally {
            closeRecordStore();
            if (dataOut != null) {
                try {
                    dataOut.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (byteOut != null) {
                try {
                    byteOut.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    public void writeAll(Storable[] objects) {
        if ((objects == null) || (objects.length == 0)) {
            // nothing to write
            return;
        }
        openRecordStore();
        try {
            for (int i = 0; i < objects.length; i++) {
                writeRecord(objects[i]);
            }
        } finally {
            closeRecordStore();
        }
    }

    private void openRecordStore() {
        try {
            recordStore = RecordStore.openRecordStore(name, true, RecordStore.AUTHMODE_PRIVATE, false);
        } catch (RecordStoreException e) {
            throw new StoreException("Unable to open record store " + name, e);
        }
    }

    private void closeRecordStore() {
        try {
            recordStore.closeRecordStore();
        } catch (RecordStoreException e) {
            ErrorHandler.log.append("Failed to close record store " + name + " due to " + e + "\n");
        }
    }

    private RecordEnumeration getRecordEnumeration(byte recordType) {
        recordFilter.recordType = recordType;
        try {
            return recordStore.enumerateRecords(recordFilter, null, false);
        } catch (RecordStoreNotOpenException e) {
            throw new StoreException("Record store " + name + " not open", e);
        }
    }

}


